
// Fragment template engine.
var Fragment = {

    // Property accessor function for fragment objects.
    _propAccessor: function(name, value) {
        var propDef = this._properties[name];
        if (arguments.length == 1) {
            return this[name] = propDef.node[propDef.domName];
        } else {
            propDef.node[propDef.domName] = this[name] = value;
        }
    },

    // Template instantiation node.
    _templInstNode: document.createElement("div"),

    // Creates a fragment from the specified template.
    create: function(template, fragment, parent) {
        
        // Applies a binding to a node.
        function applyBinding(node, type, name, domName) {
            if (type == "ref") {
                // reference
                fragment[name] = node;
                
            } else if (type == "prop") {
                // property
                fragment._properties = fragment._properties || {};
                fragment._properties[name] = {
                    node: node,
                    domName: domName
                };
                if (fragment[name] !== undefined) {
                    fragment.prop(name, fragment[name]);
                }
                
            } else if (type == "event") {
                // event
                fragment._events = fragment._events || [];
                var eventDef = {
                    node: node,
                    type: domName,
                    callback: function() {
                        if (fragment[name]) {
                            fragment[name].apply(fragment, arguments);
                        }
                    },
                    capture: false
                };
                fragment._events.push(eventDef);
                eventDef.node.addEventListener(eventDef.type, eventDef.callback, eventDef.capture);
                
            }
        }
        
        // Recursively parses bindings defined on nodes and then applies them.
        function parseBindings(node) {
            // parse and apply bindings for this node
            var bindingsAttr = node.getAttribute("bindings");
            if (bindingsAttr) {
                var bindings = bindingsAttr.split(",");
                for (var i = 0; i < bindings.length; i++) {
                    var binding = bindings[i].split(":");
                    applyBinding(node, binding[0], binding[1], binding[2]);
                }
            }
            
            // recursively process children
            var child = node.firstChild;
            while (child) {
                if (child.nodeType == 1) {
                    parseBindings(child);
                }
                child = child.nextSibling;
            }
        }
        
        // add property accessor function to fragment
        fragment.prop = fragment.prop || this._propAccessor;
        
        // instantiate the template
        if (typeof template == "string") {
            this._templInstNode.innerHTML = template;
            fragment.root = this._templInstNode.firstChild;
        } else {
            fragment.root = template.cloneNode(true);
        }
        
        // parse and apply bindings
        parseBindings(fragment.root);
        
        if (parent) {
            // connect to parent
            parent.appendChild(fragment.root);
        } else if (fragment.root.parentNode) {
            // disconnect fragment
            fragment.root.parentNode.removeChild(fragment.root);
        }
        
        return fragment;
    },
    
    // Destroys a fragment.
    destroy: function(fragment) {
        // disconnect from parent
        if (fragment.root.parentNode) {
            fragment.root.parentNode.removeChild(fragment.root);
        }
        
        // remove all event listeners
        if (fragment._events) {
            for (var i = 0; i < fragment._events.length; i++) {
                var eventDef = fragment._events[i];
                eventDef.node.removeEventListener(eventDef.type, eventDef.callback, eventDef.capture);
            }
        }
        
        return fragment;
    }
    
};

